-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial implementation for Keysight QCS #944
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #944 +/- ##
==========================================
- Coverage 50.55% 47.37% -3.19%
==========================================
Files 63 71 +8
Lines 2886 3080 +194
==========================================
Hits 1459 1459
- Misses 1427 1621 +194
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
3de5459
to
1a4d052
Compare
@sorewachigauyo I previously rebased https://github.com/qiboteam/qibolab/tree/0.2 on current https://github.com/qiboteam/qibolab/tree/main, thus I also rebased your PR consequently. The Rust workflow is failing for unknown reasons (nothing changed on our side, so the reason should be in some dependency, but it seems non-reproducible locally). P.S.: same for QuTiP, which is making the other workflows to fail as well (but we should merge the PR very soon) |
c14a227
to
41dd538
Compare
Initial testing with readout spectroscopy using runcard https://github.com/qiboteam/qibolab_platforms_nqch/tree/qibolab-0.2/iqm5q import numpy as np
import matplotlib.pyplot as plt
from qibolab import (
AcquisitionType,
AveragingMode,
Parameter,
Sweeper,
create_platform,
)
platform = create_platform("iqm5q")
platform.connect()
qubit = platform.qubits[4]
natives = platform.natives.single_qubit[4]
sequence = natives.MZ.create_sequence()
f0 = platform.config(qubit.probe).frequency # center frequency
sweeper = Sweeper(
parameter=Parameter.frequency,
range=(f0 - 20e6, f0 + 100e6, 1e6),
channels=[qubit.probe],
)
results = platform.execute([sequence],
[[sweeper]],
nshots=100,
relaxation_time=100e3,
averaging_mode=AveragingMode.SEQUENTIAL,
acquisition_type=AcquisitionType.INTEGRATION)
_, acq = next(iter(sequence.acquisitions))
# plot the results
signal = results[acq.id]
amplitudes = np.abs(signal[..., 0] + 1j * signal[..., 1])
frequencies = sweeper.values
plt.title("Resonator Spectroscopy")
plt.xlabel("Frequencies [GHz]")
plt.ylabel("Amplitudes [a.u.]")
plt.plot(frequencies / 1e9, amplitudes * 1e6)
plt.savefig("test.png") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the lengthy review 😅
src/qibolab/_core/instruments/qcs.py
Outdated
for channel_id, input_op in acquisitions: | ||
channel = self.virtual_channel_map[channel_id] | ||
if channel not in acquisition_map: | ||
acquisition_map[channel] = [] | ||
acquisition_map[channel].append(input_op) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In principle, we could even lift the construction of such a mapping to the PulseSequence
itself (do you think it may be useful elsewhere? @stavros11)
Of course, it would be a ChannelId -> [InputOps]
mapping, that you could supplement with your ChannelId -> qcs.Channels
mapping to obtain the desired result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @sorewachigauyo, now it seems extremely cleaner.
I'm sorry that a bunch of functions right now require a lot of arguments. We could try to find a better way to split some parts, partially evaluate tasks, or better package the arguments.
But this is really the kind of refactor that can be postponed (almost forever...).
Most of the comments are on the sweepers, which is frequently the most critical part. All the others seem pretty nice (though I may spend a bit more time later on proposing a small refactor about the array manipulations in the result - but nothing that serious, it's already pretty succinct).
for idx2, sweeper in enumerate(parallel_sweeper): | ||
qcs_variable = qcs.Scalar( | ||
name=f"V{idx}_{idx2}", value=sweeper.values[0], dtype=float | ||
) | ||
|
||
if sweeper.parameter is Parameter.frequency: | ||
sweeper_channel_map.update( | ||
{channel_id: qcs_variable for channel_id in sweeper.channels} | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry @sorewachigauyo, this is not about this PR, but rather Qibolab's overall design. I'm writing this here because it is an optimal example to discuss, and you're invited to provide your opinion as well. I may lift this immediately as a separate issue, I'm just undecided.
With @stavros11, we wondered at some point whether to strip away the option of having multiple channels and pulses swept by a single sweeper (not a parallel one). In fact, this could be now replaced by an equivalent set of parallel sweepers.
We didn't do that because of the specific implementation of parallel sweepers in QM, which requires the usage of a for_each_
context manager, which had some disadvantages with respect to a plain for_
.
It now appears to me that quite redundant, since here you have explicitly three nested iterations:
idx
about the sweepers' groupidx2
within the parallel sweepers- and
sweeper.channels
(orsweeper.pulses
down below - exact analogue)
These could have been decreased by one level by unraveling the .channels
in the parallel group.
However, it is true that, thanks to the presence of multiple .channels
, you are able to use a single qcs_variable
, and this could be an advantage, and a reason to keep the current behavior (which we would keep anyhow, but we may consider deprecating at some point - and finally removing in 0.3).
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a strong opinion of the subject as I can go either way and have been hacking the driver implementation to fit the ongoing design.
Though, since Qibolab 0.3 is targeting program-level control, it may be possible to pass that program directly to a FPGA without much conversion/parsing layers in the driver. Also, unraveling the channel/pulse arrays could help in maybe a Qibolab FPGA implementation by reducing the complexity required to implement sweepers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to clarify: Qibolab itself is not planning to become an FPGA design, but the idea is to have a high-level language to be compiled to the individual ones. But at the same time, I'd like to keep it as close as possible to the existing languages to minimize the conversion (i.e. compilation) efforts.
If then you're willing to contribute to the language design, and you may want to use it as the basis to design an FPGA-based processor (at which point, it would require essentially no conversion), you're definitely more than welcome :P
I like that as well, e.g. the strictly modular structure in Rust, in which you need to declare any visibility beyond the current namespace. However, that's not Python style, and even The Python philosophy is to leave everything public, since
(not able to find a proper reference, sorry, but it is a GvR quote) The only way to go beyond that are naming conventions, and I'd suggest avoiding double leading underscores, unless you're extremely confident (but it's unfamiliar and a bit confusing...). So, whatever is inside |
9b5f6f8
to
11ba3cb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another tiny proposal for the results handling, but other than that it looks good to me.
I'll wait for @stavros11 review, but it may be good to go :)
Thanks @sorewachigauyo for your effort!
5e490c1
to
3a48f83
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @sorewachigauyo for this. I left some comments below, most are not very important and mainly cosmetics, I would approve immediately if it weren't for the last two comments (mainly the last one), which I consider the most important. The main issue with these is that they are interface facing, and it would be good to address here in order to avoid breaking later.
In any case, as I mentioned also in the comments, I am have never used these instruments in practice, so if you prefer to keep the existing approach it is also fine with me. I am more concerned with avoid breaking in the future, than the exact approach used.
Also, I have not read all resolved comments from the previous reviews as I was not following last week, so I am sorry if something was already discussed before.
elif isinstance(shape, (Gaussian, Drag)): | ||
return qcs.GaussianEnvelope(shape.rel_sigma) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the motivation to include Drag
here? It seems that this always returns a Gaussian, completely ignoring the DRAG beta
parameter. If DRAG is not available then doesn't it make more sense to just raise an error, like for all other shapes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the DRAG is implemented here at https://github.com/qiboteam/qibolab/pull/944/files#diff-e3c18ab9440f0e68561a15f5e9497097a405ef090fd2612f4f3a362c71c83502R126-R127
and I need the gaussian as the base envelope for the summation of the base and derivative envelope.
(https://quantumbenchmark.com/docs/qcs/api/channels.html#rj-drag)
I could implement the DRAG based on Qibolab's implementation, but I would not like to create a static envelope especially when duration sweepers can change the envelope size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sorewachigauyo I understand your concern, and now that you explained I'm even fine with your solution.
Not to enforce the change request, but as a general note, we suspect that most of the APIs are actually implementing duration sweepers in the same inefficient way you would do using Qibolab's envelopes (i.e. evaluating on software all the required arrays and uploading them).
This is not happening for QM, but there is a dedicated feature linked to that, and we're now exposing it as a separate sweeper (where the envelope is not uploaded multiple times, but interpolated on the device).
qibolab/src/qibolab/_core/sweeper.py
Line 25 in f74c334
duration_interpolated = (auto(), _PULSE) |
If you suspect Keysight to be able to interpolate as well, I'd recommend you to verify it (as much as you can), and expose it through that sweeper, instead of the bare duration one (there will be a fallback at some level, but they may have different caveats, related to timing and approximation).
# Duration is not supported with hardware sweeping | ||
if sweeper.parameter is Parameter.duration: | ||
hardware_sweeping = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd guess that it should be possible to support this by uploading multiple waveforms, which is the default solution we are using for QM and I believe we will also use in Qblox and others.
It is fine to not do this in this PR. If you are interested in doing it some time in the future, you can open an issue for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am told duration sweeping will eventually be supported, so I think this is fine for now to make the backend layer handle it in software (which I assume is exactly the implementation you are describing right now, just not explicit).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure?
The implementation @stavros11 is talking about is inefficient, since it requires multiple arrays uploads, but not as inefficient as what we usually call "a software sweeper", since the multiple arrays will be uploaded on the device, and they will be executed without any networking, nor any memory exchange on the device itself. While a software sweeper (for us) is just a software loop, repeating many separate experiments and downloading the results at each iteration
# Currently nested hardware sweeping is not supported | ||
hardware_sweeping = len(hardware_sweepers + software_sweepers) == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can also be an issue, as it may be useful for various routines, in terms of performance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am told its on the roadmap.
# Map of QCS virtual channels to QCS physical channels | ||
qcs_channel_map: qcs.ChannelMapper | ||
# Map of Qibolab channel IDs to QCS virtual channels | ||
virtual_channel_map: dict[ChannelId, qcs.Channels] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the example platform here(*): https://github.com/qiboteam/qibolab_platforms_nqch/blob/qibolab-0.2/iqm5q/platform.py, I have the impression that this dictionary can be eliminated by using qibolab's channel.path
, which I believe is currently not used. I am not sure if types can be worked out though, because our channel.path
should be a str
. If qcs.ChannelMapper
provides string keys, you may be able to retain the information using just qcs_channel_map
and channel.path
, otherwise it may be tricky.
In any case, I have no experience with these particular instruments, so if you find it easier to use it this way, it is fine with me.
(*) Unrelated to the above, it would be useful to also have an example (not necessarily real/working) platform here, but we can postpone this for later, when we have better testing or even better documentation for how to use each driver.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to have a direct mapping because other approaches would involve some type casting or eval to get the necessary parameters from the channel
object and start to warp the model.
The qcs.ChannelMapper
does not refer to channels by string keys but offers an array view to the virtual channel bundles.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the updates and responses @sorewachigauyo. Other than the unresolved discussions above, mainly about the duration sweep and generally software vs hardware sweeps, which I guess that we all agree we can postpone for later, this looks good to me for merging.
It would be good if you can test on the actual device before merging this, as we cannot do that on our side. My main concern is the last change adding the QcsAcquisitionConfig
, which looks good in terms for code, however I saw that the platform in the other repo has not been updated to use it and we should probably verify if it works.
Thanks @stavros11 for the review!
@sorewachigauyo, I guess it would be ideal if you can just repeat a whatever scan, or even publish a report with a few routines from qiboteam/qibocal#990 Other than that, I would aim to merge asap, to get further traction for 0.2. |
I have tested this on my machine.
Unfortunately, it would be next week earliest as we are currently installing the full control electronics package and other maintenance works on the fridge. |
Trial implementation of the core functions. Testing is rough on my side since I do not currently have access to the instrument library on my development machine, so I have to test the code on the embedded controller.
Checklist: